package com.gitee.wsl.coroutines.strategy

import com.gitee.wsl.ext.base.currentTimeMillis
import com.gitee.wsl.ext.reflect.TParams
import com.gitee.wsl.ext.reflect.VoidFunction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration

/**
 * Description:
 *
 * @author Junerver date: 2024/1/30-11:02 Email: junerver@gmail.com
 *     Version: v1.0
 */
data class ThrottleOptions(
    var wait: Duration = 1.seconds, // 节流时长
    var leading: Boolean = true, // true：第一个任务是不延时
    var trailing: Boolean = true, // true：将最后一次点击添加到延时任务中
) {
    companion object {
        val instance = ThrottleOptions()
    }
}

class Throttle(
    private val scope: CoroutineScope,
    private val options: ThrottleOptions = ThrottleOptions.instance,
    var fn: VoidFunction,
) : VoidFunction {
    constructor(
        scope: CoroutineScope,
        wait: Duration = 1.seconds, // 节流时长
        fn: VoidFunction
    ):this(scope,ThrottleOptions(wait),fn)


    // 调用计数
    private var calledCount = 0

    // 任务 -- 是否结束边缘
    private val trailingJobs: MutableList<Job> = arrayListOf()

    // 执行成功时间
    private var latestInvokedTime = 0L
    private fun clearTrailing() {
        if (trailingJobs.isNotEmpty()) {
            trailingJobs.forEach {
                it.cancel()
            }
            trailingJobs.clear()
        }
    }

    override fun invoke(p1: TParams) {
        val (wait, leading, trailing) = options
        val waitTime = (currentTimeMillis - latestInvokedTime).toDuration(DurationUnit.MILLISECONDS)

        fun task(isDelay: Boolean, isTrailing: Boolean = false) {
            // 尾随任务不自动运行
            scope.launch(start = if (isTrailing) CoroutineStart.LAZY else CoroutineStart.DEFAULT) {
                if (isDelay) delay(wait)
                fn(p1)
                if (!isTrailing && trailingJobs.isNotEmpty()) {
                    // 非尾随任务执行完毕后清空尾随任务
                    trailingJobs.last().apply {
                        start()
                        join()
                    }
                }
            }.also {
                if (isTrailing) {
                    trailingJobs.add(it)
                }
            }
        }
        if (waitTime > wait) {
            task(isDelay = !(calledCount == 0 && leading))
            latestInvokedTime = currentTimeMillis
        } else {
            // 有常规任务
            if (trailing) {
                // 移除全部尾任务
                clearTrailing()
                // 追加一个结束边缘,结束边缘不clear
                task(isDelay = true, isTrailing = true)
            }
        }
        calledCount++
    }
}

fun CoroutineScope.throttle(options: ThrottleOptions = ThrottleOptions.instance,fn: VoidFunction) = Throttle(
    this,
    options,
    fn
)


/**
 * Limit the rate at which a [block] is called.
 * The [block] execution is triggered upon reception of [Unit] from the returned [SendChannel].
 *
 * @param wait The time in ms between each [block] call.
 *
 * @author p-lr
 */
fun CoroutineScope.throttle(wait: Long, block: () -> Unit): SendChannel<Unit> {

    val channel = Channel<Unit>(capacity = 1)
    val flow = channel.receiveAsFlow()
    launch {
        flow.collect {
            block()
            delay(wait)
        }
    }
    return channel
}